package org.scalpel.HHObjects;

import org.scalpel.common.HHObject;

import ioio.lib.api.DigitalOutput;
import ioio.lib.api.IOIO;
import ioio.lib.api.IOIOFactory;
import ioio.lib.api.exception.ConnectionLostException;


import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;


/**
 * @author Jerome Monceaux 
 * @author Sylvere Billout
 * @version 1.0
 * 
 * Send ioio events to all subscribed modules
 **/
public class IOIOManager extends HHObject{
  private static final String TAG = "IOIOManager";
  /** Is the phone connected to the board */
  private boolean fIsConnected = false;
  
  /** Is the initialization already done. */
  private boolean fIsInit = false;
  
  /** Is it a pure simulation */
  public boolean fIsSimulation = false;
  
  /** Cycle time (in millisecond) **/
  public final static int UPDATE_TIME = 20; 
  private int fElapsedTime; // process duration without sleep
  private int fLastOverflowedTime = 0;
  
  /** thread to update the ioio's devices */ 
  private volatile IOIOThread ioio_thread_ = null;

  /** Name of the preference file **/
  public static final String PREFS_NAME = "scalpel";
  
  
  public IOIOManager(String name) {
    super(name);
  }
  
  public void startLoop(){
	Log.d(TAG,"Start Loop");
    ioio_thread_ = new IOIOThread();
    ioio_thread_.start();
  }
  
  public void stopLoop(){
	Log.d(TAG,"Stop Loop");
    if(ioio_thread_ != null){
      Thread moribund = ioio_thread_;
      ioio_thread_ = null;
      moribund.interrupt();
      try {
        moribund.join(500);
      } catch (InterruptedException e) {
      }
    }
  }
  
 
  /** onDraw notification of registered module */
  public void onDraw(Canvas canvas, Paint paint){
    canvas.save();
    final int connectedPosY = 473; // position in the screen
    final int connectedPosX = 685;
    String statusString = "IOIO ";
    if( !fIsInit )
    {
      paint.setColor(Color.GRAY);
      canvas.drawCircle(connectedPosX, connectedPosY, 3, paint);
      statusString += "not init";
      canvas.drawText(statusString, connectedPosX+10, connectedPosY+5, paint);
      return;
    }
    
    if( !fIsConnected ){
      paint.setColor(Color.RED);
      statusString += "not connected";
    }
    else{
      paint.setColor(Color.GREEN);
      statusString += "ok";
    }
    
    canvas.drawCircle(connectedPosX, connectedPosY, 3, paint);
    canvas.drawText(statusString, connectedPosX+10, connectedPosY+5, paint);
    
    int e = 0;
    synchronized(this){
      e = fElapsedTime;
    }
    if (e <= UPDATE_TIME)
      paint.setColor(Color.GREEN);
    else
    {
      paint.setColor(Color.RED);
      fLastOverflowedTime = e;
    }
    
    canvas.drawText("Update Time : " + Integer.toString(UPDATE_TIME)
        //+" Time cycle : " + Integer.toString(e)
        +" Last overflow : " + Integer.toString(fLastOverflowedTime)
        +" Time load : " + Integer.toString((int)(100*(float)e / UPDATE_TIME)) + "%"
        , 5, connectedPosY, paint);
    
    canvas.restore();
  }
  
  /** onInitView notification of registered module */
  public void onInitView(Context context) {
    // read preference : start with simulation mode or not ? 
    SharedPreferences settings = context.getSharedPreferences(PREFS_NAME, 0);
    fIsSimulation = settings.getBoolean("simulation", true);
  }

  
  /**
	 * Subclasses may override this method for alternate creation modes of the
	 * IOIO instance.
	 * 
	 * @return A IOIO instance.
	 */
	protected IOIO createIOIO() {
		Log.d(TAG,"Create IOIO");
		return IOIOFactory.create();
	}
  
  /**
   * An abstract class, which facilitates a thread dedicated for IOIO
   * communication.
   */
  protected class IOIOThread extends Thread {
    /** Subclasses should use this field for controlling the IOIO. */
    protected IOIO ioio_ = null;
    private boolean abort_ = false;
    private DigitalOutput led_;
    
    private long fLastTime;
    private long fCurrentTime;
      /**
     * Subclasses should override this method for performing operations to
     * be done once as soon as IOIO communication is established. Typically,
     * this will include opening pins and modules using the openXXX()
     * methods of the {@link #ioio_} field.
     */
    synchronized protected void setup( ) throws ConnectionLostException {
      if( !fIsSimulation && ioio_ != null){
    	Log.d(TAG,"Setup");
        led_ = ioio_.openDigitalOutput(IOIO.LED_PIN, false);
      }
      
      // notify subscribers 
      setChanged();
      notifyObservers(new Object[]{"initIOIO", new Object[]{fIsSimulation, ioio_}});

      
      fLastTime = System.currentTimeMillis();
      
      if( !fIsSimulation && ioio_ != null){
        led_.write(false); // turn the led on
      }
      fIsInit = true;    
    }

    /**
     * Subclasses should override this method for performing operations to
     * be done repetitively as long as IOIO communication persists.
     * Typically, this will be the main logic of the application, processing
     * inputs and producing outputs.
     */
    synchronized protected void loop() throws ConnectionLostException {

      try {
        
        // notify subscribers
        setChanged();
        notifyObservers(new Object[]{"updateIOIO", null});
         
        int elapsed = 0;
        fCurrentTime = System.currentTimeMillis();
        elapsed = (int) (fCurrentTime-fLastTime);
        
        
        if(elapsed < UPDATE_TIME/2)
          Thread.sleep(UPDATE_TIME/4, 0); // Sleep, but not too much, OS could forget to wake us up.
        
        while(elapsed < UPDATE_TIME){ // The best way to sleep... but the most CPU expensive
          fCurrentTime = System.currentTimeMillis();
          elapsed = (int) (fCurrentTime-fLastTime);
        }

        synchronized(this){
          fElapsedTime = elapsed;
        }

        // ALTERNATIVE 2
        //SLEEPER.poll(elapsed, TimeUnit.MILLISECONDS);
        
        // ALTERNATIVE 3
        //LockSupport.parkNanos(elapsed*1000);
        
        //ALTERNATIVE 4 
        /*try {
          Thread.sleep(elapsed/2, 0);
          long currTime = SystemClock.elapsedRealtime();
          sleepNanos((elapsed-elapsed2)*1000);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }*/
        

        fCurrentTime = System.currentTimeMillis();
        elapsed = (int) (fCurrentTime-fLastTime);
        fLastTime = fCurrentTime;
        
      } catch (InterruptedException e) {
        ioio_.disconnect();
      }
    }

    /** Not relevant to subclasses. */
    @SuppressWarnings("finally")
    @Override
    public final void run() {
      super.run();
      
        while (Thread.currentThread() == ioio_thread_) {
          // Set the highest priority possible. We need to keep the cycle duration constant 
          Thread.currentThread().setPriority(MAX_PRIORITY);
          try {
            synchronized (this) {
              if (abort_) {
                break;
              }
              ioio_ = createIOIO();
            }
            if(!fIsSimulation){
              ioio_.waitForConnect(); // This is a blocking method waiting for the board connection. 
              // No way to switch the simulation automatically. 
              fIsConnected = true;
              Log.d(TAG,"Ioio connected");
            }else{
              fIsConnected = false;
              Log.d(TAG,"Ioio not connected");
            }
            setup();
            
            while (Thread.currentThread() == ioio_thread_) {
              loop(); // call the main loop
            }
          } catch (ConnectionLostException e) {
            if (abort_) {
              break;
            }
          } catch (Exception e) {
            Log.e("IOIOThread",
                "Unexpected exception caught", e);
            ioio_.disconnect();
            break;
          } finally {
            // If an error occurs it would be nice to switch to simulation mode but...
            break;
          }
        }
      
    }

    /** Not relevant to subclasses. */
    public synchronized final void abort() {
      abort_ = true;
      if (ioio_ != null) {
        ioio_.disconnect();
      }
    }
  }
  

  /** return true if the board is correctly connected */
  public boolean isConnected() {
    return fIsConnected;
  }

  
  /** return true if the setup of the control thread of the board has been done */
  public boolean isInit() {
    return fIsInit;
  }

  @Override
  public void finish() {
  	if( ioio_thread_ != null)
  		ioio_thread_.abort();
    stopLoop();
  }
}
